﻿
--[[    
    SwiftDiscipline
    Copyright (C) 2008 Twist3d aka Lifetapt
  
    This file is part of SwiftDiscipline.

    SwiftDiscipline is free software: you can redistribute it 
    and/or modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation, either version 3 of the 
    License, or (at your option) any later version.

    SwiftDiscipline is distributed in the hope that it will 
    be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 
    of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
    General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with SwiftDiscipline.  If not, see <http://www.gnu.org/licenses/>.
]]--

--===================================================================================================================--
-- Name:        SwiftDiscipline
-- Version:     1.01.20400
-- Description: SwiftDiscipline attempts to make disciplining players on Ascent servers easier for GMs by hooking into
--              certain Blizzard dropdown menus and adding shortcuts to player discipline commands such as kick, ban,
--              IPban, mute, etc. It also allows the GM to quickly .appear and .summon players, .recall portplayer
--              players, resurrect, and paralyze players.
--              NOTE: This addon is designed to work with ArcEmu commands and system messages. It may not work with
--              other emulators or newer revs of ArcEmu if commands or system messages (like pinfo output) are changed.
--              Testing for this addon was done using ArcEmu r579.
-- Author:      Twist3d
--===================================================================================================================--

--===================================================================================================================--
--  Declarations
--===================================================================================================================--

SwDi = {}     --addon table
SDVars = {Config = {},
          stats = {},
          banDB = {},
          }   --addon data storage
SWDI_VERSION = "1.01";

--Default Settings==================>
  SDVars.Config = {
    minimapPos = -7,
    hideMinimap = false,
    kickSaved1 = "PvPing in a non-PvP area",
    kickSaved2 = "Excessive use of caps-lock in a public channel",
    kickSaved3 = "Failing to use common sense",
    kickSaved4 = "Exploit abusing/hacking",
    kickSaved5 = "Relog",
    kickSaved6 = "Defiance/disrespect towards GM",
    kickSaved7 = "Vulgar language",
    kickSaved8 = "Sending empty/useless tickets",
    kickSaved9 = "Spamming",
    kickSaved10 = "Extreme levels of fail detected.",
    censorip = false,
    disableann = false,
    disableconsider = false,
    closeall = false,
  };

--Database Variables=========>
local kickDB = {};          --stores recent kicks
SDVars.banDB = {};
SDVars.stats = {kicks = 0,
                accbans = 0,
                ipbans = 0,
                mutes = 0,
                ports = 0,
                revives = 0,
                paras = 0,
                }
SDVars.daystats = {kicks = 0,
                  accbans = 0,
                  ipbans = 0,
                  mutes = 0,
                  ports = 0,
                  revives = 0,
                  paras = 0,
                  today = date("%m/%d/%y");
                }
SDVars.currentRev = 0;

--Command Constants=================>
local SWDI_CMDKICK = ".kick ";
local SWDI_CMDABAN = ".account ban ";
local SWDI_CMDIBAN = ".addipban ";

--Miscellaneous Variables===========>
local waitingForPInfo = 0;
--0 = not waiting, 1 = waiting, 2 = completed, 3 = ban/mute in progress
local banType = 0;
--0 = noban, 1 = accountban, 2 = ipban, 3 = mute
local banTarget, banDuration, banReason, banPlayerName;
local targetPermissions;

--Time Variables====================>
local secondsString, minutesString, hoursString, daysString;

--Dialog Boxes======================>
  StaticPopupDialogs["SWDICONSIDERBAN"] = {
    text = "[Shouldn't see this] This player has been kicked several times in this session. You might want to consider a temporary ban next.",
    button1 = "Dismiss",
    timeout = 6,
    whileDead = 1,
    hideOnEscape = 1,
    showAlert = 0,
  };
  StaticPopupDialogs["SWDICONFIRMABAN"] = {
    text = "[Shouldn't see this] Whip out the banstick on this player's account?",
    button1 = "Yes",
    button2 = "Cancel Ban",
    OnAccept = function()
      waitingForPInfo = 3;
      SwDi:StartTimer(10);  --timeout counter, after 10 seconds the action will fail to prevent the addon from getting stuck in a waiting state
      SwDi:DoABan();
    end,
    OnCancel = function()
      waitingForPInfo = 0;
      SwDi_ABanPromptProgress:SetText("Ban canceled.");
    end,
    timeout = 40,
    whileDead = 1,
    hideOnEscape = 0,
    showAlert = 1,
  };
  StaticPopupDialogs["SWDICONFIRMIBAN"] = {
    text = "[Shouldn't see this] Do you wish to drop teh banhammer on all connections from this IP?",
    button1 = "Yes",
    button2 = "Cancel Ban",
    OnAccept = function()
      waitingForPInfo = 3;
      SwDi:StartTimer(10);
      SwDi:DoIBan();
    end,
    OnCancel = function()
      waitingForPInfo = 0;
      SwDi_IBanPromptProgress:SetText("Ban canceled.");
    end,
    timeout = 40,
    whileDead = 1,
    hideOnEscape = 0,
    showAlert = 1,
  };

--Validates the SDVars table after variables have been loaded. Can also be used to reset to default if clearall is true.
function SwDi:validateSettings(clearall, clearstats)
  if SDVars.Config == nil or clearall then SDVars.Config = {}; end
  if SDVars.stats == nil or clearall or clearstats then SDVars.stats = {}; end
  if SDVars.daystats == nil or clearall or clearstats then SDVars.daystats = {}; end
  if SDVars.banDB == nil or clearall then SDVars.banDB = {}; end
  if SDVars.currentRev == nil then SDVars.currentRev = 0; end
  --validate individiual settings
  ---validate config
  if not SDVars.Config.minimapPos then SDVars.Config.minimapPos = -7; end
  if not SDVars.Config.hideMinimap then SDVars.Config.hideMinimap = false; end
  if not SDVars.Config.kickSaved1 then SDVars.Config.kickSaved1 = "PvPing in a non-PvP area"; end
  if not SDVars.Config.kickSaved2 then SDVars.Config.kickSaved2 = "Excessive use of all-caps in a public channel"; end
  if not SDVars.Config.kickSaved3 then SDVars.Config.kickSaved3 = "Failing to use common sense"; end
  if not SDVars.Config.kickSaved4 then SDVars.Config.kickSaved4 = "Exploit abusing/hacking"; end
  if not SDVars.Config.kickSaved5 then SDVars.Config.kickSaved5 = "Relog"; end
  if not SDVars.Config.kickSaved6 then SDVars.Config.kickSaved6 = "Defiance/disrespect towards GM"; end
  if not SDVars.Config.kickSaved7 then SDVars.Config.kickSaved7 = "Vulgar language in public channel"; end
  if not SDVars.Config.kickSaved8 then SDVars.Config.kickSaved8 = "Sending empty/useless tickets"; end
  if not SDVars.Config.kickSaved9 then SDVars.Config.kickSaved9 = "Spamming"; end
  if not SDVars.Config.kickSaved10 then SDVars.Config.kickSaved10 = "Extreme levels of fail detected"; end
  if SDVars.Config.censorip == nil then SDVars.Config.censorip = false; end
  if SDVars.Config.disableann == nil then SDVars.Config.disableann = false; end
  if SDVars.Config.disableconsider == nil then SDVars.Config.disableconsider = false; end
  if SDVars.Config.closeall == nil then SDVars.Config.closeall = false; end
  --validate lifetime stats
  if not SDVars.stats.kicks then SDVars.stats.kicks = 0; end
  if not SDVars.stats.accbans then SDVars.stats.accbans = 0; end
  if not SDVars.stats.ipbans then SDVars.stats.ipbans = 0; end
  if not SDVars.stats.mutes then SDVars.stats.mutes = 0; end
  if not SDVars.stats.ports then SDVars.stats.ports = 0; end
  if not SDVars.stats.revives then SDVars.stats.revives = 0; end
  if not SDVars.stats.paras then SDVars.stats.paras = 0; end
  --validate today stats
  if not SDVars.daystats.kicks then SDVars.daystats.kicks = 0; end
  if not SDVars.daystats.accbans then SDVars.daystats.accbans = 0; end
  if not SDVars.daystats.ipbans then SDVars.daystats.ipbans = 0; end
  if not SDVars.daystats.mutes then SDVars.daystats.mutes = 0; end
  if not SDVars.daystats.ports then SDVars.daystats.ports = 0; end
  if not SDVars.daystats.revives then SDVars.daystats.revives = 0; end
  if not SDVars.daystats.paras then SDVars.daystats.paras = 0; end
  if not SDVars.daystats.today then SDVars.daystats.today = date("%m/%d/%y"); end
  if SDVars.daystats.today ~= date("%m/%d/%y") then
    --today is a new day, reset daily stats
    SDVars.daystats.kicks = 0;
    SDVars.daystats.accbans = 0;
    SDVars.daystats.ipbans = 0;
    SDVars.daystats.mutes = 0;
    SDVars.daystats.ports = 0;
    SDVars.daystats.revives = 0;
    SDVars.daystats.paras = 0;
    SDVars.daystats.today = date("%m/%d/%y");
  end
  
end

--===================================================================================================================--
--  Timer Frame
--===================================================================================================================--

local timer=CreateFrame("Frame")
timer:Hide()

local tInterval

timer:SetScript("OnUpdate", function(self, elapsed)
  if waitingForPInfo == 0 or waitingForPInfo == 2 then self:Hide(); end
  if countdown <= 0 then
    waitingForPInfo = 0
    if banType == 1 or banType == 2 then
      SwDi_ABanPromptProgress:SetText("Ban attempt timed out.");
      SwDi_IBanPromptProgress:SetText("Ban attempt timed out.");
      SwDi:Msg("Action failed. Ban attempt timed out.\nPlease check the parameters you supplied and make sure your connection to the server is alive.");
    else
      SwDi_MutePromptProgress:SetText("Mute attempt timed out.");
      SwDi:Msg("Action failed. Mute attempt timed out.\nPlease check the parameters you supplied and make sure your connection to the server is alive.");
    end
    banType = 0;
    self:Hide()
  else
    countdown = countdown - elapsed
  end
end)

function SwDi:StartTimer(duration)
  countdown = duration
  timer:Show()
end


--===================================================================================================================--
--  Event Handlers
--===================================================================================================================--

function SwDi:OnLoad()

  SLASH_SWDICONF1 = "/sd";
  SLASH_SWDICONF2 = "/swift";
  SLASH_SWDICONF3 = "/swiftdiscipline";
  SLASH_SWDIRL1 = "/rl";
  SlashCmdList["SWDICONF"] = function(msg) SwDi:SlashHandler(msg); end
  SlashCmdList["SWDIRL"] = function(msg) ReloadUI(); end
  
end


function SwDi:OnEvent(event)
  if (event == "PLAYER_ENTERING_WORLD") then

  elseif (event == "VARIABLES_LOADED") then
    SwDi:validateSettings(false);
    SwDi:Msg("Twist3d's SwiftDiscipline "..SWDI_VERSION.." loaded.\nUse /sd or /swift for command list.")
    if (GetLocale() ~= "enUS" and GetLocale() ~= "enGB") and SwDi_CHATLOCALIZEDBY then
      SwDi:Msg("You are using an unsupported locale. The addon may not function as intended.")
    end
    SwDi:MinimapButton_Reposition()
  end
end

function SwDi:OnDragStart(self)

  UIFrameFadeOut(self, 0.15, 1, 0.3);
  self:StartMoving(); 

end

function SwDi:OnDragStop(self)

  if self:GetAlpha() ~= 1 then UIFrameFadeIn(self, 0.15, 0.3, 1); end
  self:StopMovingOrSizing();

end

--===================================================================================================================--
--  Hooks/Filters/Dropdownmenu Insertion
--===================================================================================================================--

--==================================
--= Dropdown Menu Handling/Hooking =
--==================================
--This section deals with adding the extra SwiftDiscipline buttons to the player rightclick and chat rightclick dropdown
--menus (known as unitpopup menus). To start, we will create our buttons in UnitPopupButtons, and create our menus in
--UnitPopupMenus. Then we add the buttons as children of whatever menus they need to appear under.
--After that, we will securehook (tainting UnitPopup_OnClick will break a bunch of functions) UnitPopup_OnClick to add
--click event code for our buttons.

--I am really really really hoping there isn't some sort of huge performance hit caused by adding all these menu items...
--I would have made a simple five-button menu if this wasn't just so damn convienient.

function SwDi:initDropdowns()

  --Create our dropdown for the unitpopup.
  
  --Create our menu and submenu buttons.
  UnitPopupButtons["SWDIKICKBAN"] = {text = "|cFF00BEFFKick/Ban", dist = 0, nested = 1, tooltipText = "|cFF00BEFFShows commands for kicking, banning, or muting the selected player."};
  UnitPopupButtons["SWDIPORT"] = {text = "|cFF00BEFFPort", dist = 0, nested = 1, tooltipText = "|cFF00BEFFShows commands for porting, appearing, or summoning the selected player."};
  ----Create all the dropdown menu buttons for our menus and submenus.
  --KickBan submenu
  UnitPopupButtons["SWDIKICK"] = {text = "|cFF00BEFFKick", dist = 0};
  UnitPopupButtons["SWDIABAN"] = {text = "|cFF00BEFFAccount Ban", dist = 0};
  UnitPopupButtons["SWDIIBAN"] = {text = "|cFF00BEFFIP Ban", dist = 0};
  UnitPopupButtons["SWDIMUTE"] = {text = "|cFF00BEFFMute", dist = 0};
  --Port submenu
  UnitPopupButtons["SWDIPORTWIN"] = {text = "|cFF00BEFFPort", dist = 0};
  UnitPopupButtons["SWDIAPPEAR"] = {text = "|cFF00BEFFAppear", dist = 0};
  UnitPopupButtons["SWDISUMMON"] = {text = "|cFF00BEFFSummon", dist = 0};
  --Base menu buttons
  UnitPopupButtons["SWDIREVIVE"] = {text = "|cFF00BEFFResurrect", dist = 0, tooltipText = "|cFF00BEFFInstantly resurrects the selected player."};
  --Blue cancel button
  UnitPopupButtons["SWDICANCEL"] = {text = "|cFF00BEFFCancel", dist = 0};
  --insert our first dropdown button (which is a nested dropdown) into PLAYER (the target right-click menu) and FRIEND (the chat rightclick menu)
	tinsert(UnitPopupMenus["PLAYER"], #UnitPopupMenus["PLAYER"] - 1, "SWDIKICKBAN");
  tinsert(UnitPopupMenus["PLAYER"], #UnitPopupMenus["PLAYER"] - 1, "SWDIPORT");
  tinsert(UnitPopupMenus["PLAYER"], #UnitPopupMenus["PLAYER"] - 1, "SWDIREVIVE");
	tinsert(UnitPopupMenus["FRIEND"], #UnitPopupMenus["FRIEND"] - 1, "SWDIKICKBAN");
  tinsert(UnitPopupMenus["FRIEND"], #UnitPopupMenus["FRIEND"] - 1, "SWDIPORT");
  tinsert(UnitPopupMenus["FRIEND"], #UnitPopupMenus["FRIEND"] - 1, "SWDIREVIVE");
  
  --create our dropdown menus and submenus, with our buttons as children of our menus.
  UnitPopupMenus["SWDIKICKBAN"] = {"SWDIKICK", "SWDIABAN", "SWDIIBAN", "SWDIMUTE", "SWDICANCEL"};
  UnitPopupMenus["SWDIPORT"] = {"SWDIPORTWIN", "SWDIAPPEAR", "SWDISUMMON", "SWDICANCEL"};

  hooksecurefunc("UnitPopup_OnClick", function() SwDi:UnitPopup_OnClick(); end);
end

function SwDi:UnitPopup_OnClick()
	local unitpopupFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
	local button = this.value;
	if(button == "SWDIREVIVE") then 
    SwDi:Send(".reviveplr "..unitpopupFrame.name); 
  elseif (button == "SWDIKICK") then
    SwDi:ShowKick(unitpopupFrame.name);
  elseif (button == "SWDIABAN") then
    SwDi:ShowAccBan(unitpopupFrame.name);
  elseif (button == "SWDIIBAN") then
    SwDi:ShowIPBan(unitpopupFrame.name);
  elseif (button == "SWDIPORTWIN") then
    SwDi:ShowPort(unitpopupFrame.name);
  elseif (button == "SWDIAPPEAR") then
    SwDi:Send(".appear "..unitpopupFrame.name);
  elseif (button == "SWDISUMMON") then
    SwDi:Send(".summon "..unitpopupFrame.name);
  elseif (button == "SWDIMUTE") then
    SwDi:ShowMute(unitpopupFrame.name);
  end
end

SwDi:initDropdowns();
  

  
  
--===================================
--= CHAT_MSG_SYSTEM Filter Function =
--===================================
--This filter function parses all incoming CHAT_MSG_SYSTEM messages for the purposes of stat-tracking, kick-counting,
--and parsing .playerinfo for a player's IP address and accountname.
function SwDi_SystemMsgFilter(msg)  
  
  if strfind(msg, "Server: ") then
    local currentRev = SwDi:ParseRev(msg);
    if currentRev then SDVars.currentRev = currentRev; end
    if currentRev < 583 then
      SwDi:Msg("Server is running revision "..currentRev.."... SwiftDiscipline will use the 'old' set of GM commands.");
    else
      SwDi:Msg("Server is running revision "..currentRev.."... SwiftDiscipline will use the 'new' set of GM commands.");
      SWDI_CMDKICK = ".kickplayer ";
      SWDI_CMDABAN = ".ban account ";
      SWDI_CMDIBAN = ".ban ip ";
    end
  end
  
  if strfind(msg, "GM: (.+) was kicked from the server by") then
    local name, gm, reason = SwDi:ParseKick(msg);
    if not kickDB[name] then kickDB[name] = {kicks = 0}; end
    kickDB[name].kicks = kickDB[name].kicks + 1;
    if gm == UnitName("player") then SwDi:incrementStat("kicks"); end
    if gm == UnitName("player") and SwDi_KickPrompt:IsVisible() then SwDi_KickPrompt:Hide(); end
  end
  
  if strfind(msg, "Revived player (.+).") then
    if SwDi_RevivePrompt:IsVisible() then SwDi_RevivePrompt:Hide(); end
    SwDi:incrementStat("revives");
  end
  
  if strfind(msg, "Rooting target.") then
    SwDi:incrementStat("paras");
  end

  if strfind(msg, "has been muted until") then
    SwDi:incrementStat("mutes");
    waitingForPInfo = 0;
    SwDi_MutePromptProgress:SetText("Account "..banTarget.." has been muted.");
    SwDi_MutePrompt:Hide();
  end
  
  if strfind(msg, "Appearing at ") or strfind(msg, "You are summoning ") or strfind(msg, "%(Offline%) (.+) has been summoned") then
    SwDi:incrementStat("ports");
  end
  
  if strfind(msg, "Account \'(.+)\' has been banned") then
    SwDi:incrementStat("accbans");
    waitingForPInfo = 0;
    if SwDi_ABanPrompt:IsVisible() and not SDVars.Config.disableann then SwDi:Send(".ann [SwiftDiscipline] "..banPlayerName.." (account "..banTarget..") has been banned from the server by "..UnitName("player")..". Reason: "..banReason.."; Duration: "..SwDi:formatDuration(banDuration)); end
    SwDi_ABanPromptProgress:SetText("Account "..banTarget.." has been banned.");
    SwDi_ABanPrompt:Hide();
  end
  
  if strfind(msg, "to IP ban table") then
    SwDi:incrementStat("ipbans");
    waitingForPInfo = 0;
    if SwDi_IBanPrompt:IsVisible() then SwDi:Send(".ann [SwiftDiscipline] "..banPlayerName.." (IP "..SwDi:censorIP(banTarget)..") has been IP-banned from the server by "..UnitName("player")..". Reason: "..banReason.."; Duration: "..SwDi:formatDuration(banDuration)); end
    SwDi_IBanPromptProgress:SetText("IP "..banTarget.." has been banned.");
    SwDi_IBanPrompt:Hide();
  end
  
  if SwDi_PortPrompt:IsVisible() then
    if strfind(msg, "recall ports player") or strfind(msg, "Teleports to x's") or strfind(msg, "Summons x to your") then
      if strfind(msg, "recall ports player") then SDVars.stats.ports = SDVars.stats.ports - 1; end
      SwDi:Msg("Teleport failed.");
      return true;
    end
  end
  
  if SwDi_MutePrompt:IsVisible() then
    if strfind(msg, "Mutes account for ") then
      SwDi:Msg("Invalid duration for mute (mutes cannot have a zero/infinite duration).");
      SwDi_MutePromptProgress:SetText("Invalid duration for mute.");
      waitingForPInfo = 0;
      return true;
    end
  end
  

  
  --When requesting a ban, the addon sets the variable waitingForPInfo to 1 (2 is finished). From that point forward,
  --this code block will run and look for the playerinfo lines containing a player's IP address, GM permissions, and
  --account name. This data is on two separate lines (Account and permissions on one line, and IP on another).
  if waitingForPInfo == 1 or waitingForPInfo == 2 then
  
    --Without this block the player still sees the "She uses WoW Burning Crusade" and the IP address line (for account
    --bans where it is not needed).
    if waitingForPInfo == 2 then --at 2, we are waiting for the user to accept or cancel the ban.
      if strfind(msg, "uses WoW ") then  --we still want to discard these two lines,
        return true;
      end
      if strfind(msg, "and has a latency") then     --but we don't want to discard other non pinfo lines.
        return true;
      end
      if strfind(msg, "or you do not have") then    --the user doesn't have access to the required command.
        waitingForPInfo = 0
        if banType == 1 then SwDi_ABanPromptProgress:SetText("You do not have access to "..SWDI_CMDABAN.."command."); end
        if banType == 2 then SwDi_IBanPromptProgress:SetText("You do not have access to "..SWDI_CMDIBAN.."command."); end
        if banType == 3 then SwDi_IBanPromptProgress:SetText("You do not have access to .account mute command."); end
        SwDi:Msg("Action failed. SwiftDiscipline couldn't send the ban command because you do not have access to it.");
        return true;
      end
      return false;
    end
  
    if strfind(msg, "Unable to locate player") then
      waitingForPInfo = 0
      if banType == 1 then SwDi_ABanPromptProgress:SetText("Unable to find player."); end
      if banType == 2 then SwDi_IBanPromptProgress:SetText("Unable to find player."); end
      if banType == 3 then SwDi_MutePromptProgress:SetText("Unable to find player."); end
      SwDi:Msg("Action failed. SwiftDiscipline couldn't lookup player information (player does not exist or has logged out).");
    end
    
    --This is the account/permissions line. Even if we're doing an IP ban we want to check the player's GM permissions.
    if strfind(msg, "is connecting from account") then
      if banType == 1 then 
        SwDi_ABanPromptProgress:SetText("Got playerinfo, waiting for user confirmation..."); 
      elseif banType == 3 then 
        SwDi_MutePromptProgress:SetText("Got playerinfo, muting account..."); 
      else 
        SwDi_IBanPromptProgress:SetText("Got playerinfo, waiting for user confirmation..."); 
      end
      targetPermissions = SwDi:ParsePerm(msg);    --parse player permissions
      
      if banType == 1 or banType == 3 then banTarget = SwDi:ParseAcc(msg); waitingForPInfo = 2; end   --set bantarget for an account-ban/mute
      if banType == 1 then    --setup the confirmation dialog boxes
        StaticPopupDialogs["SWDICONFIRMABAN"].text = "Are you sure you wish to ban this player's account?";
      elseif banType == 2 then
        StaticPopupDialogs["SWDICONFIRMIBAN"].text = "Are you sure you wish to ban all connections from this IP?";
      end
      
      if targetPermissions ~= nil then    --append a big warning we're about to ban a player with GM permissions.
        StaticPopupDialogs["SWDICONFIRMABAN"].text = StaticPopupDialogs["SWDICONFIRMABAN"].text.."\n\nWARNING: Ban target has GM permissions!"
        StaticPopupDialogs["SWDICONFIRMIBAN"].text = StaticPopupDialogs["SWDICONFIRMIBAN"].text.."\n\nWARNING: Ban target has GM permissions!"
      end
      
      if banType == 1 then StaticPopup_Show("SWDICONFIRMABAN"); end   --if we're doing an account ban, we're done parsing info, so show the confirmation box
      if banType == 3 then SwDi:DoMute(); end
    elseif (strfind(msg, ", and has a latency") and banType == 2) then    --...otherwise wait until next line of pinfo, which contains the IP address.
      waitingForPInfo = 2
      banTarget = SwDi:ParseIP(msg);  --set the ban target, dialog boxes were already set during the last line
      StaticPopup_Show("SWDICONFIRMIBAN")
    end
    return true;
    
  else
    return false;
  end

end

ChatFrame_AddMessageEventFilter("CHAT_MSG_SYSTEM", SwDi_SystemMsgFilter); --And finally add the filter function we just wrote.

--===================================================================================================================--
--  Helper Functions
--===================================================================================================================--

--The following four functions parse IP, Account, Permissions, and Kick information from system messages.
--The first three are used for the .playerinfo command, to find out which IP/Account should be banned, and
--if they are a GM, by playername.
--If the strings change in any future revision of ascent/arcemu/aspire/whatever, they will have to be changed
--here or the addon will not be able to get IP/Account/Permission data and will probably throw errors.
function SwDi:ParseRev(infomsg)
  local rev;
  _, _, _, rev =  string.find(infomsg, "Server:(.+)r(%d+)");
  return tonumber(rev);
end

function SwDi:ParseIP(pinfo)
  local ip;
  _, _, ip =  string.find(pinfo, "IP is \'(.+)\', and has a latency");
  return ip;
end

function SwDi:ParseAcc(pinfo)
  local acc;
  _, _, acc, _ =  string.find(pinfo, "connecting from account \'(.+)\'%[(.+)%]");
  return acc;
end

function SwDi:ParsePerm(pinfo)
  local perms;
  _, _, perms =  string.find(pinfo, "with permissions \'(.+)\'");
  return perms;
end

function SwDi:ParseKick(kickmsg)
  local reason
  _, _, kicked, gm, reason = string.find(kickmsg, "GM: (.+) was kicked from the server by (.+). Reason: (.+)");
  return kicked, gm, reason
end

function SwDi:censorIP(ip)  --censors the last two octets of an ip address, ie. 212.52.xxx.xx. If censorip config option is false, just returns the uncensored IP.
  if not SDVars.Config.censorip then return ip; end
  local _, _, octet1, octet2, octet3, octet4 = string.find(ip, "(%d+)%.(%d+)%.(%d+)%.(%d+)")
  return (octet1.."."..octet2.."."..string.rep("x", string.len(octet3)).."."..string.rep("x", string.len(octet4)))
end

function SwDi:formatDuration(duration) --formats a duration code (1y 2m 3d 4h) to a duration string (1 year, 2 months, 3 days, 4 hours)
  local years, months, days, hours;    --this function creates probably many more garbage strings than it needs to.
  local appendStr, temp, newDuration;
  local numOfValues = 0;
  _, _, years = strfind(duration, "(%d-)y");
  _, _, months = strfind(duration, "(%d-)m");
  _, _, days = strfind(duration, "(%d-)d");
  _, _, hours = strfind(duration, "(%d-)h");
  temp = "";
  newDuration = "";
  if years ~= nil then
    if years == "1" then appendStr = "year"; else appendStr = "years"; end
    temp = years.." "..appendStr;
    numOfValues = numOfValues + 1;
  end
  newDuration = temp;
  if months ~= nil then
    temp = "";
    if numOfValues ~= 0 then temp = ", "; end
    if months == "1" then appendStr = "month"; else appendStr = "months"; end
    temp = temp..months.." "..appendStr;
    numOfValues = numOfValues + 1;
    newDuration = newDuration..temp
  end
  
  if days ~= nil then
    temp = "";
    if numOfValues ~= 0 then temp = ", "; end
    if days == "1" then appendStr = "day"; else appendStr = "days"; end
    temp = temp..days.." "..appendStr;
    numOfValues = numOfValues + 1;
    newDuration = newDuration..temp
  end
  
  if hours ~= nil then
    temp = "";
    if numOfValues ~= 0 then temp = ", "; end
    if hours == "1" then appendStr = "hour"; else appendStr = "hours"; end
    temp = temp..hours.." "..appendStr;
    numOfValues = numOfValues + 1;
    newDuration = newDuration..temp;
  end
  
  if newDuration == "" then newDuration = "Forever"; end
  
  return newDuration;
end

function SwDi:Msg(msg)
  DEFAULT_CHAT_FRAME:AddMessage("|cFF006EFFSwiftDiscipline:|r |cFF00BEFF"..msg.."|r");
end

--Main GM command function, sends to guild channel to guarantee the command is sent
function SwDi:Send(command)
  SendChatMessage(command, "GUILD");
end

--Kick, increment the kick counter, and show a "You might want to ban them next" box if the player has been kicked 3 times.
function SwDi:Kick(player, reason)
  if not kickDB[player] then kickDB[player] = {kicks = 0}; end
  if reason == nil or reason == "" then reason = "No reason"; end
  SendChatMessage(SWDI_CMDKICK..player.." [SwiftDiscipline] "..reason.."; Kick number "..tostring(kickDB[player].kicks + 1).."", "GUILD");
  if (kickDB[player].kicks + 1) > 3 and not SDVars.Config.disableconsider then StaticPopupDialogs["SWDICONSIDERBAN"].text = "This player has been kicked "..(kickDB[player].kicks + 1).." times in this session. You might want to consider a temporary ban for the next offense."; StaticPopup_Show("SWDICONSIDERBAN"); end
end

--The ban functions have two parts, the Ban function and the DoBan function. The Ban function is called when the player clicks
--ban. It initializes the ban by asking for playerinfo and setting banType and waitingForPInfo. Then it's up to our chat filter
--to do some parsing and setup, and finally control is passed to DoBan after the player clicks Yes to the confirmation dialog.
function SwDi:ABan(player, duration, reason)
  if reason == nil or reason == "" then reason = "No reason"; end
  if waitingForPInfo ~= 0 then return; end
  SwDi_ABanPromptProgress:SetText("Retrieving player information...");
  banPlayerName = player;
  banDuration = duration;
  banReason = reason;
  banType = 1;
  waitingForPInfo = 1;
  SwDi:StartTimer(10);
  SwDi:Send(".playerinfo "..player);
end

function SwDi:DoABan()
  SwDi_ABanPromptProgress:SetText("Banning account "..banTarget.."...");
  if banReason == nil or banReason == "" then banReason = "None given."; end
  if banDuration == nil or banDuration == "" or banDuration == "0" then banDuration = "Forever"; end
  SwDi:Send(SWDI_CMDABAN..banTarget.." "..banDuration);
end

function SwDi:IBan(player, duration, reason)
  if reason == nil or reason == "" then reason = "No reason"; end
  if waitingForPInfo ~= 0 then return; end
  SwDi_IBanPromptProgress:SetText("Retrieving player information...");
  banPlayerName = player;
  banDuration = duration;
  banReason = reason;
  banType = 2;
  waitingForPInfo = 1;
  SwDi:StartTimer(10);
  SwDi:Send(".playerinfo "..player);
end

function SwDi:DoIBan()
  SwDi_IBanPromptProgress:SetText("Banning IP address "..banTarget.."...");
  if banReason == nil or banReason == "" then banReason = "None given."; end
  if banDuration == nil or banDuration == "" or banDuration == "0" then banReason = "999y"; end --mute tool does not support "Forever" mutes.
  SwDi:Send(SWDI_CMDIBAN..banTarget.." "..banDuration);
end

function SwDi:Mute(player, duration)
  if waitingForPInfo ~= 0 then return; end
  SwDi_MutePromptProgress:SetText("Retrieving player information...");
  banPlayerName = player;
  banDuration = duration;
  banType = 3;
  waitingForPInfo = 1;
  SwDi:StartTimer(10);
  SwDi:Send(".playerinfo "..player);
end

function SwDi:DoMute()
  SwDi_MutePromptProgress:SetText("Muting account "..banTarget.."...");
  if banDuration == nil or banDuration == "" or banDuration == "0" then banReason = "Forever"; end
  SwDi:Send(".account mute "..banTarget.." "..banDuration);
end

function SwDi:round(num, decimalplaces)
  local mult = 10^(decimalplaces or 0)
  return math.floor(num * mult + 0.5) / mult
end

function SwDi:TimeToString(time, useShort) --Seconds-to-timestring util function
  if useShort then
    secondsString = "seconds";
    minutesString = "minutes";
    hoursString = "hours";
    daysString = "days"
  else
    secondsString = "s";
    minutesString = "m";
    hoursString = "h";
    daysString = "d";
  end
  local ret = "";
  local units = 0;

  if( time < 0 ) then
    return "";
  end

  local days = floor( time / 86400 );

  if( days > 0 ) then
    time = time - days * 86400;
    ret = days.." "..daysString.." ";
    units = units + 1;
  end

  local hours = floor( time / 3600 );
  if( hours > 0 ) then
    time = time - hours * 3600;
    ret = ret .. hours.." "..hoursString.." ";
    units = units + 1;
  end

  if( units == 2 ) then
    return ret;
  end

  local minutes = floor ( time / 60 );
  if( minutes > 0 ) then
    time = time - minutes * 60;
    ret = ret .. minutes.." "..minutesString.." ";
    units = units + 1;
  end

  if( units == 2 ) then
    return ret;
  end

  return ret .. time.." "..secondsString;
end

function SwDi:incrementStat(stat)
  SDVars.stats[stat] = SDVars.stats[stat] + 1;
  SDVars.daystats[stat] = SDVars.daystats[stat] + 1;
end

function SwDi:ToBoolean(value)

  if (value == nil) then
    return(false);
  end

  if (type(value) == 'boolean') then
    return(value);
  end
  return(true);

end

function SwDi:CloseAll()
  SwDi_KickPrompt:Hide();
  SwDi_ABanPrompt:Hide();
  SwDi_IBanPrompt:Hide();
  SwDi_MutePrompt:Hide();
  SwDi_PortPrompt:Hide();
  SwDi_RevivePrompt:Hide();
  SwDi_StatPrompt:Hide();
  SwDi_DayStatPrompt:Hide();
  SwDi_EditSavedPrompt:Hide();
  SwDi_Config:Hide();
end

--===================================================================================================================--
--  Slash Handling
--===================================================================================================================--

--The slash command parsing code was found on WoWWiki, written by Tigerheart.
function SwDi:GetArgument(msg)
 	if msg then
 		local a,b=strfind(msg, "(%S+)");
 		if a then
 			return strsub(msg,1,a-1), strsub(msg, b+1);
 		else	
 			return "";
 		end
 	end
end

function SwDi:GetCmd(msg)
  if msg then
 		local a,b,c=strfind(msg, "(%S+)"); 
 		if a then
 			return c, strsub(msg, b+2);
 		else	
 			return "";
 		end
 	end
end

--Our main slash command handler.
function SwDi:SlashHandler(arg)
  local cmd, subCmd = SwDi:GetCmd(arg);
  if cmd == "reset" then
    SwDi:validateSettings(true);
    ReloadUI();
  elseif cmd == "repos" then
    SwDi_Main:SetUserPlaced(false);
    SwDi_Mini:SetUserPlaced(false);
    SwDi_KickPrompt:SetUserPlaced(false);
    SwDi_ABanPrompt:SetUserPlaced(false);
    SwDi_IBanPrompt:SetUserPlaced(false);
    SwDi_MutePrompt:SetUserPlaced(false);
    SwDi_PortPrompt:SetUserPlaced(false);
    SwDi_RevivePrompt:SetUserPlaced(false);
    SwDi_StatPrompt:SetUserPlaced(false);
    SwDi_DayStatPrompt:SetUserPlaced(false);
    SwDi_EditSavedPrompt:SetUserPlaced(false);
    SwDi_Config:SetUserPlaced(false);
    ReloadUI();
  elseif cmd == "config" or cmd == "options" or cmd == "settings" then
    if SDVars.Config.closeall then SwDi:CloseAll(); end
    SwDi_Config:Show();    
  elseif cmd == nil or cmd == "" then
    SwDi:Msg("Slash Command List");
    DEFAULT_CHAT_FRAME:AddMessage("|cFF00BEFF".."/swift kick [player]\n|rOpens kick window. (NYI)");
    DEFAULT_CHAT_FRAME:AddMessage("|cFF00BEFF".."/swift aban [player]\n|rOpens account ban window. (NYI)");
    DEFAULT_CHAT_FRAME:AddMessage("|cFF00BEFF".."/swift iban [player]\n|rOpens IP ban window. (NYI)");
    DEFAULT_CHAT_FRAME:AddMessage("|cFF00BEFF".."/swift mute [player]\n|rOpens mute window. (NYI)");
    DEFAULT_CHAT_FRAME:AddMessage("|cFF00BEFF".."/swift port [player]\n|rOpens teleport window. (NYI)");
    DEFAULT_CHAT_FRAME:AddMessage("|cFF00BEFF".."/swift config\n|rOpens options window.");
    DEFAULT_CHAT_FRAME:AddMessage("|cFF00BEFF".."/swift reset \n|rResets settings to default (reloads UI).");
    DEFAULT_CHAT_FRAME:AddMessage("|cFF00BEFF".."/swift repos \n|rResets window positions (reloads UI).");
    DEFAULT_CHAT_FRAME:AddMessage("|cFF00BEFF".."Most slash commands are not yet implemented.");
  else
    SwDi:Msg(string.format("Unrecognized command (%s). Type /sd for command list.", cmd));
  end
  
end

--===================================================================================================================--
--  Prompt Window Functions
--===================================================================================================================--
--A lot of this code is residue from my Paranoia addon, which I used as a sort of skeleton for Swift. It isn't used, yet,
--but I might add some configuration settings later so it might as well stay here.

function SwDi:OptionCheckBoxClicked(option)

  SDVars.Config[option] = SwDi:ToBoolean(this:GetChecked());
  
end

function SwDi:ShowKick(player)
  if SDVars.Config.closeall then SwDi:CloseAll(); end
  if player == nil then 
    if UnitName("target") ~= nil and UnitIsPlayer("player") ~= nil then player = UnitName("target"); else player = ""; end
  end
  SwDi_KickPrompt:Show()
  SwDi_KickPromptPlayerName:SetText(player);
end

function SwDi:ShowAccBan(player)
  if SDVars.Config.closeall then SwDi:CloseAll(); end
  if player == nil then 
    if UnitName("target") ~= nil and UnitIsPlayer("player") ~= nil then player = UnitName("target"); else player = ""; end
  end
  SwDi_ABanPrompt:Show()
  SwDi_ABanPromptPlayerName:SetText(player);
end

function SwDi:ShowIPBan(player)
  if SDVars.Config.closeall then SwDi:CloseAll(); end
  if player == nil then 
    if UnitName("target") ~= nil and UnitIsPlayer("player") ~= nil then player = UnitName("target"); else player = ""; end
  end
  SwDi_IBanPrompt:Show()
  SwDi_IBanPromptPlayerName:SetText(player);
end

function SwDi:ShowMute(player)
  if SDVars.Config.closeall then SwDi:CloseAll(); end
  if player == nil then 
    if UnitName("target") ~= nil and UnitIsPlayer("player") ~= nil then player = UnitName("target"); else player = ""; end
  end
  SwDi_MutePrompt:Show()
  SwDi_MutePromptPlayerName:SetText(player);
end

function SwDi:ShowPort(player)
  if SDVars.Config.closeall then SwDi:CloseAll(); end
  if player == nil then 
    if UnitName("target") ~= nil and UnitIsPlayer("player") ~= nil then player = UnitName("target"); else player = ""; end
  end
  SwDi_PortPrompt:Show()
  SwDi_PortPromptPlayerName:SetText(player);
end

function SwDi:ShowRevive(player)
  if SDVars.Config.closeall then SwDi:CloseAll(); end
  if player == nil then 
    if UnitName("target") ~= nil then player = UnitName("target"); else player = ""; end
  end
  if UnitIsDeadOrGhost("player") then
    SwDi:Msg("You are dead, resurrecting self.");
    SwDi:Send(".reviveplr "..UnitName("player"));
  else
    if player ~= "" and UnitIsDeadOrGhost("target") ~= nil and UnitIsPlayer ~= nil then SwDi:Send(".reviveplr "..player); else SwDi_RevivePrompt:Show(); end
  end
end

function SwDi:Paralyze(player)
  if player == nil then 
    if UnitName("target") ~= nil then player = UnitName("target"); else player = ""; end
  end
  if player ~= "" and UnitIsPlayer("target") ~= nil then SwDi:Send(".paralyze "..player); else SwDi:Msg("Invalid target for paralyzation."); end
end

function SwDi:ShowStats()
  if SDVars.Config.closeall then SwDi:CloseAll(); end
  SwDi_StatPrompt:Show()
end

function SwDi:OptionCheckBoxInit(option)

  this:SetChecked(SwDi:ToBoolean(SDVars.Config[option]));

end

function SwDi:SliderMoved(option)

  SDVars.Config[option] = this:GetValue();

end

function SwDi:SliderInit(option)

  this:SetValue(SDVars.Config[option]);

end

--===================================================================================================================--
--  Minimap Button Functions (using Gello's minimap button template from the WoWI forums)
--===================================================================================================================--

function SwDi:MinimapButton_Reposition()
	SwDi_MinimapButton:SetPoint("TOPLEFT","Minimap","TOPLEFT",52-(80*cos(SDVars.Config.minimapPos)),(80*sin(SDVars.Config.minimapPos))-52)
end

function SwDi_MinimapButtonDraggingFrame_OnUpdate()

	local xpos,ypos = GetCursorPosition()
	local xmin,ymin = Minimap:GetLeft(), Minimap:GetBottom()

	xpos = xmin-xpos/UIParent:GetScale()+70 -- get coordinates as differences from the center of the minimap
	ypos = ypos/UIParent:GetScale()-ymin-70
  
	SDVars.Config.minimapPos = math.deg(math.atan2(ypos,xpos)) -- save the degrees we are relative to the minimap center
	SwDi:MinimapButton_Reposition() -- move the button
end

function SwDi_MinimapButton_OnClick()
	if arg1 == "LeftButton" then
    if SwDi_Main:IsVisible() then 
      SwDi_Main:Hide();
      SwDi_Mini:Show();
    else
      SwDi_Main:Show();
      SwDi_Mini:Hide();
    end
  else
    if SDVars.Config.closeall then SwDi:CloseAll(); end
    SwDi_Config:Show();
  end
end

function SwDi:UpdateMinimapVisibility()
  
  if SDVars.Config.hideMinimap then
    SwDi_MinimapButton:Hide();
  else
    SwDi_MinimapButton:Show();
  end
  
end